const Fabric_Client = require('fabric-client');
const Fabric_CA_Client = require('fabric-ca-client');
const path = require('path');
const fs = require("fs");
const util = require('util');
const fabric_util = require("./fabric_util");


module.exports = {

    query: async function (obj) {
        const fabric_client = new Fabric_Client();
        try {
            let channel = fabric_client.newChannel(obj.channel);
            let peer = fabric_client.newPeer(`grpcs://${obj.p_adr_core}`, {
                'pem': Buffer.from(obj.p_tls).toString(),
                'ssl-target-name-override': obj.peer
            });

            channel.addPeer(peer);

            //获取key-store地址
            let store_path = await fabric_util.storePath(obj.msp).then((store_path) => {
                return store_path
            });

            return await Fabric_Client.newDefaultKeyValueStore({
                path: store_path

            }).then((state_store) => {
                fabric_client.setStateStore(state_store);
                let crypto_suite = Fabric_Client.newCryptoSuite();
                let crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
                crypto_suite.setCryptoKeyStore(crypto_store);
                fabric_client.setCryptoSuite(crypto_suite);

                return fabric_client.getUserContext(obj.ca_name, true);

            }).then((user_from_store) => {
                if (!(user_from_store && user_from_store.isEnrolled())) {
                    return this.enroll(fabric_client, store_path, obj).then(() => {
                        let request = {
                            chaincodeId: obj.chaincode,
                            fcn: obj.fcn,
                            args: obj.args
                        };

                        return channel.queryByChaincode(request);
                    }).catch((e) => {
                        return e;
                    });
                } else {
                    let request = {
                        chaincodeId: obj.chaincode,
                        fcn: obj.fcn,
                        args: obj.args
                    };
                    console.log(request)
                    return channel.queryByChaincode(request);
                }

            }).then((query_responses) => {
                if (query_responses && query_responses.length === 1) {
                    if (query_responses[0] instanceof Error) {
                        console.error("error from query = ", query_responses[0]);
                        return "Could not locate tuna1"
                    } else {
                        console.log("Response is ", query_responses[0].toString());
                        return query_responses[0].toString()
                    }
                } else {
                    console.log("No payloads were returned from query");
                    return "Could not locate tuna2";
                }
            }).catch((err) => {
                console.log(1 + err)
                return err
            });
        } catch (e) {
            console.log(3 + e)
            return e
        }
    },


    invoke: async function (obj) {
        const fabric_client = new Fabric_Client();
        let reponse = {};
        try {
            let channel = fabric_client.newChannel(obj.channel);
            //一、创建通信节点
            let peer = fabric_client.newPeer(`grpcs://${obj.p_adr_core}`, {
                'pem': Buffer.from(obj.p_tls).toString(),
                'ssl-target-name-override': obj.peer
            });
            channel.addPeer(peer);
            //二、创建组织节点
            let order = fabric_client.newOrderer(`grpcs://${obj.order_adr_core}`, {
                'pem': Buffer.from(obj.order_tls).toString(),
                'ssl-target-name-override': obj.order
            });
            channel.addOrderer(order);

            //三、获取key-store地址
            let store_path = await fabric_util.storePath(obj.msp).then((store_path) => {
                return store_path
            });
            let tx_id = null;
            return await Fabric_Client.newDefaultKeyValueStore({
                path: store_path

            }).then((state_store) => {
                fabric_client.setStateStore(state_store);
                let crypto_suite = Fabric_Client.newCryptoSuite();
                let crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
                crypto_suite.setCryptoKeyStore(crypto_store);
                fabric_client.setCryptoSuite(crypto_suite);

                return fabric_client.getUserContext(obj.ca_name, true);

            }).then((user_from_store) => {

                if (!(user_from_store && user_from_store.isEnrolled())) {
                    return this.enroll(fabric_client, store_path, obj).then(() => {
                        tx_id = fabric_client.newTransactionID();
                        let request = {
                            chaincodeId: obj.chaincode,
                            fcn: obj.fcn,
                            args: obj.args,
                            chainId: obj.channel,
                            txId: tx_id
                        };

                        return channel.sendTransactionProposal(request);
                    }).catch((e) => {
                        return e;
                    });
                } else {
                    tx_id = fabric_client.newTransactionID();
                    let request = {
                        chaincodeId: obj.chaincode,
                        fcn: obj.fcn,
                        args: obj.args,
                        chainId: obj.channel,
                        txId: tx_id
                    };
                    return channel.sendTransactionProposal(request);
                }

            }).then((results) => {
                let proposalResponses = results[0];
                reponse = proposalResponses;
                let proposal = results[1];
                let isProposalGood = false;
                if (proposalResponses && proposalResponses[0].response &&
                    proposalResponses[0].response.status === 200) {
                    isProposalGood = true;
                    console.log('Transaction proposal was good');

                } else {
                    console.error('Transaction proposal was bad');

                }
                if (isProposalGood) {
                    let request = {
                        proposalResponses: proposalResponses,
                        proposal: proposal
                    };

                    let transaction_id_string = tx_id.getTransactionID();
                    let promises = [];

                    let sendPromise = channel.sendTransaction(request);
                    promises.push(sendPromise);

                    let event_hub = fabric_client.newEventHub();
                    event_hub.setPeerAddr(`grpcs://${obj.p_adr_event}`, {
                        'pem': Buffer.from(obj.p_tls).toString(),
                        'ssl-target-name-override': obj.peer
                    });


                    let txPromise = new Promise((resolve, reject) => {
                        let handle = setTimeout(() => {
                            event_hub.disconnect();
                            resolve({event_status: 'TIMEOUT'});
                        }, 3000);
                        event_hub.connect();
                        let connneted = event_hub.isconnected();
                        // console.log(connneted)
                        event_hub.registerTxEvent(transaction_id_string, (tx, code) => {

                            clearTimeout(handle);
                            event_hub.unregisterTxEvent(transaction_id_string);
                            event_hub.disconnect();

                            let return_status = {event_status: code, tx_id: transaction_id_string};
                            if (code !== 'VALID') {
                                console.error('The transaction was invalid, code = ' + code);
                                resolve(return_status);
                            } else {
                                console.log('The transaction has been committed on peer ' + event_hub._ep._endpoint.addr);
                                resolve(return_status);
                            }
                        }, (err) => {
                            reject(new Error('There was a problem with the eventhub ::' + err));
                        });
                    });
                    promises.push(txPromise);

                    return Promise.all(promises);
                } else {
                    console.error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...');
                    throw new Error('Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...');
                }
            }).then((results) => {
                console.log('Send transaction promise and event listener promise have completed');
                if (results && results[0] && results[0].status === 'SUCCESS') {
                    console.log('Successfully sent transaction to the orderer.');
                    return {
                        "code": 0,
                        "tx_id": tx_id.getTransactionID(),
                        "res": reponse
                    };
                } else {
                    console.error('Failed to order the transaction. Error code: ' + response.status);
                    return {
                        "code": 200,
                        "tx_id": tx_id.getTransactionID(),
                        "res": reponse
                    };
                }

                if (results && results[1] && results[1].event_status === 'VALID') {
                    console.log('Successfully committed the change to the ledger by the peer');
                    return {
                        "code": 0,
                        "tx_id": tx_id.getTransactionID(),
                        "res": results
                    };
                } else {
                    console.log('Transaction failed to be committed to the ledger due to ::' + results[1].event_status);
                    return {
                        "code": 200,
                        "tx_id": tx_id.getTransactionID(),
                        "res": reponse
                    };
                }
            }).catch((err) => {
                console.error('Failed to invoke successfully :: ' + err);
                return {
                    "code": 200,
                    "tx_id": tx_id.getTransactionID(),
                    "res": err
                };
            });
        } catch (e) {
            console.log(3 + e)
            return e

        }
    },

    enroll: async function (fabric_client, store_path, obj) {
        return await Fabric_Client.newDefaultKeyValueStore({
            path: store_path
        }).then((state_store) => {
            fabric_client.setStateStore(state_store);
            let crypto_suite = Fabric_Client.newCryptoSuite();
            let crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
            crypto_suite.setCryptoKeyStore(crypto_store);
            fabric_client.setCryptoSuite(crypto_suite);

            let fabric_ca_client = new Fabric_CA_Client(`http://${obj.ca_address}`, null, '', crypto_suite);
            return fabric_ca_client.enroll({
                enrollmentID: obj.ca_name,
                enrollmentSecret: obj.ca_password
            });

        }).then((enrollment) => {
            return fabric_client.createUser({
                username: obj.ca_name,
                mspid: obj.msp,
                cryptoContent: {privateKeyPEM: enrollment.key.toBytes(), signedCertPEM: enrollment.certificate}
            });
        }).then((user) => {
            return fabric_client.setUserContext(user);

        }).catch((err) => {
            console.error(2 + err);
            if (err.toString().indexOf('Authorization') > -1) {
                console.error('Authorization failures may be caused by having admin credentials from a previous CA instance.\n' +
                    'Try again after deleting the contents of the store directory ' + store_path);
            }
            return err;
        });
    },

    // 20180814 added by lionel
    event: async function (obj, func) {

        const fabric_client = new Fabric_Client();
        try {
            let channel = fabric_client.newChannel(obj.channel);
            //一、创建通信节点
            let peer = fabric_client.newPeer(`grpcs://${obj.p_adr_core}`, {
                'pem': Buffer.from(obj.p_tls).toString(),
                'ssl-target-name-override': obj.peer
            });
            channel.addPeer(peer);
            //二、创建组织节点
            let order = fabric_client.newOrderer(`grpcs://${obj.order_adr_core}`, {
                'pem': Buffer.from(obj.order_tls).toString(),
                'ssl-target-name-override': obj.order
            });
            channel.addOrderer(order);

            //三、获取key-store地址
            let store_path = await fabric_util.storePath(obj.msp).then((store_path) => {
                return store_path
            });
            return await Fabric_Client.newDefaultKeyValueStore({
                path: store_path

            }).then((state_store) => {
                fabric_client.setStateStore(state_store);
                let crypto_suite = Fabric_Client.newCryptoSuite();
                let crypto_store = Fabric_Client.newCryptoKeyStore({path: store_path});
                crypto_suite.setCryptoKeyStore(crypto_store);
                fabric_client.setCryptoSuite(crypto_suite);

                return fabric_client.getUserContext(obj.ca_name, true);

            }).then((user_from_store) => {
                if (!(user_from_store && user_from_store.isEnrolled())) {
                    return this.enroll(fabric_client, store_path, obj).then(() => {
                        let promises = [];
                        let event_hub = fabric_client.newEventHub();
                        event_hub.setPeerAddr(`grpcs://${obj.p_adr_event}`, {
                            'pem': Buffer.from(obj.p_tls).toString(),
                            'ssl-target-name-override': obj.peer
                        });


                        let chaincodeEventPromise = new Promise((resolve, reject) => {
                            event_hub.connect();
                            event_hub.registerChaincodeEvent(obj.chaincode, func, (ce) => {
                                console.log(ce.payload.toString());
                                //添加DE表
                                let entity = JSON.parse(ce.payload.toString());
                                let condition = {};
                                condition.DEDKey = entity.DepartmentKey;
                                condition.DEEKey = entity.Key;
                                condition.Desc = entity.Desc;
                                let args = [JSON.stringify(condition)];
                                let obj = fabric_util.getObj("department", "DEAdd", args);
                                let res = this.invoke(obj).then((res) => {
                                    return res;
                                });
                                console.log(res);
                            }, (err) => {
                                reject(new Error('There was a problem with the eventhub ::' + err));
                            });
                        });
                        promises.push(chaincodeEventPromise);

                        return Promise.all(promises);
                    })
                } else {
                    let promises = [];
                    let event_hub = fabric_client.newEventHub();
                    event_hub.setPeerAddr(`grpcs://${obj.p_adr_event}`, {
                        'pem': Buffer.from(obj.p_tls).toString(),
                        'ssl-target-name-override': obj.peer
                    });


                    let chaincodeEventPromise = new Promise((resolve, reject) => {

                        event_hub.connect();
                        console.log(obj.chaincode);
                        console.log(func);
                        event_hub.registerChaincodeEvent(obj.chaincode, func, (ce) => {

                            console.log(ce.payload.toString());
                            //添加DE表
                            let entity = JSON.parse(ce.payload.toString());
                            let condition = {};
                            condition.DEDKey = entity.DepartmentKey;
                            condition.DEEKey = entity.Key;
                            condition.Table = "DE";
                            if(entity.Desc!==""&&entity.Desc!==undefined){
                                condition.Desc = entity.Desc;
                            }
                            let args = [JSON.stringify(condition)];
                            let obj = fabric_util.getObj("department", "DEAdd", args);
                            let res = this.invoke(obj).then((res) => {
                                return res;
                            });
                            console.log(res);
                        }, (err) => {
                            reject(new Error('There was a problem with the eventhub ::' + err));
                        });
                    });
                    promises.push(chaincodeEventPromise);

                    return Promise.all(promises);
                }

                //console.log(fabric_client)


            }).then((results) => {
                console.log(results)
                console.log('Send event listener promise have completed');
            }).catch((err) => {
                console.error('Failed to invoke successfully :: ' + err);
                return {
                    "code": 200,
                    "res": err
                };
            });
        } catch (e) {
            console.log(3 + e)
            return e
        }
    }
}